/*
 * $Id: genpkglist.cc,v 1.7 2003/01/30 17:18:21 niemeyer Exp $
 */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <alloca.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <rpm/rpmlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>

#include <map>
#include <set>
#include <iostream>
#include <fstream>

#include <apt-pkg/error.h>
#include <apt-pkg/tagfile.h>
#include <apt-pkg/configuration.h>

#include "rapt-compat.h"
#include "crpmtag.h"
#include "cached_hash.h"
#include "genutil.h"

raptTag tags[] =  {
       RPMTAG_NAME, 
       RPMTAG_EPOCH,
       RPMTAG_VERSION,
       RPMTAG_RELEASE,
       RPMTAG_DISTTAG,
       RPMTAG_GROUP,
       RPMTAG_ARCH,
       RPMTAG_PACKAGER,
       RPMTAG_SOURCERPM,
       RPMTAG_SIZE,

       RPMTAG_DESCRIPTION,
       RPMTAG_SUMMARY,
       RPMTAG_BUILDTIME,
       /*RPMTAG_HEADERI18NTABLE*/ HEADER_I18NTABLE,

       RPMTAG_REQUIREFLAGS,
       RPMTAG_REQUIRENAME,
       RPMTAG_REQUIREVERSION,

       RPMTAG_CONFLICTFLAGS,
       RPMTAG_CONFLICTNAME,
       RPMTAG_CONFLICTVERSION,

       RPMTAG_PROVIDENAME,
       RPMTAG_PROVIDEFLAGS,
       RPMTAG_PROVIDEVERSION,

       RPMTAG_OBSOLETENAME,
       RPMTAG_OBSOLETEFLAGS,
       RPMTAG_OBSOLETEVERSION
};
int numTags = sizeof(tags) / sizeof(tags[0]);

raptTag add_tags[] =  {
       RPMTAG_CHANGELOGTIME,
       RPMTAG_CHANGELOGNAME,
       RPMTAG_CHANGELOGTEXT,

       CRPMTAG_FILENAME,
       CRPMTAG_FILESIZE,

       CRPMTAG_DIRECTORY,

       CRPMTAG_UPDATE_SUMMARY,
       CRPMTAG_UPDATE_IMPORTANCE,
       CRPMTAG_UPDATE_DATE,
       CRPMTAG_UPDATE_URL,
};
int numAddTags = sizeof(add_tags) / sizeof(add_tags[0]);

static
void copyChangelog(Header h1, Header h2, unsigned since)
{
   struct rpmtd_s times, names, texts;
   if (headerGet(h1, RPMTAG_CHANGELOGTIME, &times, HEADERGET_MINMEM) != 1)
      return;
   if (headerGet(h1, RPMTAG_CHANGELOGNAME, &names, HEADERGET_MINMEM) != 1) {
      rpmtdFreeData(&times);
      return;
   }
   if (headerGet(h1, RPMTAG_CHANGELOGTEXT, &texts, HEADERGET_MINMEM) != 1) {
      rpmtdFreeData(&times);
      rpmtdFreeData(&names);
      return;
   }
   if (times.count != names.count || names.count != texts.count) {
exit: rpmtdFreeData(&times);
      rpmtdFreeData(&names);
      rpmtdFreeData(&texts);
      return;
   }

   int n = times.count; // in
   int m = 0; // out

   if (n < 1)
      goto exit;

   unsigned *time = (unsigned *) times.data;

   for (int i = 0; i < n; i++)
      if (time[i] >= since)
	 m++;
      else
	 break;
   if (m < n)
      m++;

   headerPutUint32(h2, RPMTAG_CHANGELOGTIME, (const unsigned *) times.data, m);
   headerPutStringArray(h2, RPMTAG_CHANGELOGNAME, (const char **) names.data, m);
   headerPutStringArray(h2, RPMTAG_CHANGELOGTEXT, (const char **) texts.data, m);
   goto exit;
}


static
bool usefulFile(const char *d, const char *b,
	       const set<string> &depFiles)
{
   // PATH-like directories
   if (endswith(d, "/bin/") || endswith(d, "/sbin/"))
      return true;
   if (strcmp(d, "/usr/games/") == 0)
      return true;
   if (strcmp(d, "/usr/lib/kde4bin/") == 0)
      return true;
   // Java jars
   if (startswith(d, "/usr/share/java/") && endswith(b, ".jar"))
      return true;
   // ttf and otf fonts
   if (startswith(d, "/usr/share/fonts/") && (endswith(b, ".ttf") || endswith(b, ".otf")))
      return true;

   // shared libraries
   if (strncmp(b, "lib", 3) == 0 && strstr(b + 3, ".so"))
      return true;

   // required by other packages
   if (depFiles.find(string(d) + b) != depFiles.end()) {
      // fprintf(stderr, "useful depfile: %s%s\n", d, b);
      return true;
   }

   return false;
}


static
void copyStrippedFileList(Header h1, Header h2,
			  const set<string> &depFiles)
{
   struct {
      raptTagCount bnc, dnc, dic;
      const char **bn, **dn;
      uint32_t *di;
   }
   l1 = {0, 0, 0, NULL, NULL, NULL}, l2 = {0, 0, 0, NULL, NULL, NULL};

   struct rpmtd_s bnames, dnames, didexes;
   int rc;

   rc = headerGet(h1, RPMTAG_BASENAMES, &bnames, HEADERGET_MINMEM);
   if (rc != 1)
      return;
   assert(rpmtdType(&bnames) == RPM_STRING_ARRAY_TYPE);
   assert(rpmtdCount(&bnames) > 0);

   l1.bn = (const char**) bnames.data;
   l1.bnc = bnames.count;

   rc = headerGet(h1, RPMTAG_DIRNAMES, &dnames, HEADERGET_MINMEM);
   assert(rc == 1);
   assert(rpmtdType(&dnames) == RPM_STRING_ARRAY_TYPE);

   l1.dn = (const char**) dnames.data;
   l1.dnc = dnames.count;

   rc = headerGet(h1, RPMTAG_DIRINDEXES, &didexes, HEADERGET_MINMEM);
   assert(rc == 1);
   assert(rpmtdType(&didexes) == RPM_INT32_TYPE);

   l1.di = (uint32_t *) didexes.data;
   l1.dic = didexes.count;

   assert(l1.bnc == l1.dic);

   for (int i = 0; i < l1.bnc; i++)
   {
      const char *d = l1.dn[l1.di[i]];
      const char *b = l1.bn[i];

      if (!usefulFile(d, b, depFiles))
	 continue;

      if (l2.bnc == 0) {
         l2.bn = new const char *[l1.bnc];
         l2.dn = new const char *[l1.dnc];
         l2.di = new uint32_t[l1.dic];
      }

      l2.bn[l2.bnc++] = b;

      bool has_dir = false;
      for (int j = 0; j < l2.dnc; j++) {
         if (l2.dn[j] == d) {
            l2.di[l2.dic++] = j;
            has_dir = true;
            break;
         }
      }
      if (!has_dir) {
         l2.dn[l2.dnc] = d;
         l2.di[l2.dic++] = l2.dnc++;
      }
   }

   assert(l2.bnc == l2.dic);

   if (l2.bnc > 0) {
      headerPutStringArray(h2, RPMTAG_BASENAMES, l2.bn, l2.bnc);
      headerPutStringArray(h2, RPMTAG_DIRNAMES, l2.dn, l2.dnc);
      headerPutUint32(h2, RPMTAG_DIRINDEXES, l2.di, l2.dic);
      delete[] l2.bn;
      delete[] l2.dn;
      delete[] l2.di;
   }

   rpmtdFreeData(&bnames);
   rpmtdFreeData(&dnames);
   rpmtdFreeData(&didexes);
}


static
void findDepFiles(Header h, set<string> &depFiles, raptTag tag)
{
   struct rpmtd_s td;
   int rc = headerGet(h, tag, &td, HEADERGET_MINMEM);
   if (rc != 1)
      return;
   assert(rpmtdType(&td) == RPM_STRING_ARRAY_TYPE);
   const char **deps = (const char **) td.data;
   for (rpm_count_t i = 0; i < td.count; i++) {
      const char *dep = deps[i];
      if (*dep == '/')
         depFiles.insert(dep);
   }
   rpmtdFreeData(&td);
}


typedef struct {
   string importance;
   string date;
   string summary;
   string url;
} UpdateInfo;


static
bool loadUpdateInfo(char *path, map<string,UpdateInfo> &M)
{
   FileFd F(path, FileFd::ReadOnly);
   if (_error->PendingError()) 
   {
      return false;
   }
   
   pkgTagFile Tags(&F);
   pkgTagSection Section;
   
   while (Tags.Step(Section)) 
   {
      string file = Section.FindS("File");
      UpdateInfo info;

      info.importance = Section.FindS("Importance");
      info.date = Section.FindS("Date");
      info.summary = Section.FindS("Summary");
      info.url = Section.FindS("URL");

      M[file] = info;
   }
   return true;
}


static
void addInfoTags(Header h, const char *fname,
		 const map<string,UpdateInfo> &M)
{
   map<string,UpdateInfo>::const_iterator I = M.find(fname);
   if (I == M.end())
      return;
   const UpdateInfo &info = I->second;
   headerPutString(h, CRPMTAG_UPDATE_SUMMARY, info.summary.c_str());
   headerPutString(h, CRPMTAG_UPDATE_URL, info.url.c_str());
   headerPutString(h, CRPMTAG_UPDATE_DATE, info.date.c_str());
   headerPutString(h, CRPMTAG_UPDATE_IMPORTANCE, info.importance.c_str());
}


void usage(int rc)
{
   std::string text =
      "genpkglist " VERSION "\n"
      "usage: genpkglist [<options>] <dir> <component>\n"
      "options:\n"
      " --basedir <DIR> directory to write pkglist to instead of <dir>/base/\n"
      " --index <file>  file to write srpm index data to\n"
      " --info <file>   file to read update info from\n"
      " --useful-files <file>  file to read the list of useful files from\n"
      " --no-scan       do not scan for useful files\n"
      " --bloat         do not strip the package file list. Needed for some\n"
      "                 distributions that use non-automatically generated\n"
      "                 file dependencies\n"
      " --progress      show a progress bar\n"
      " --blake2b       add package file BLAKE2B to pkglist\n"
      " --disable-hash-caching\n"
      "                 disable caching of package file hashes\n"
      " --cachedir <DIR>\n"
      "                 use a custom directory for package hashsum cache\n"
      " --changelog-since <seconds>\n"
      "                 save package changelogs; copy changelog entries\n"
      "                 newer than seconds since the Epoch, and also\n"
      "                 one preceding entry (if any)\n"
      " --prev-stdin    read previous pkglist from stdin and use it\n"
      "                 as a cache\n"
      " --add-list <file>\n"
      "                 file to read the list of packages to add\n"
      " --rm-list <file>\n"
      "                 file to read the list of packages to remove\n"
      ;

   if (rc)
       cerr << text;
   else
       cout << text;

   exit(rc);
}


struct rec {
   const char *rpm;
   const char *srpm;
};

static
int recCmp(const void *rec1_, const void *rec2_)
{
   const struct rec *rec1 = (const struct rec *) rec1_;
   const struct rec *rec2 = (const struct rec *) rec2_;
   int cmp = strcmp(rec1->srpm, rec2->srpm);
   if (cmp)
      return cmp;
   return strcmp(rec1->rpm, rec2->rpm);
}

static
int read_list_line(ifstream &strm, struct rec *r)
{
       string line;
       if (std::getline(strm, line)) {
	   size_t del = line.find('\t');
	   string srpm = line.substr(0, del);
	   string rpm = line.substr(del + 1);
	   r->rpm = strdup(rpm.c_str());
	   r->srpm = strdup(srpm.c_str());
	   return 1;
       }
       r->rpm = NULL;
       r->srpm = NULL;
       return 0;
}

static
int processPackageFile(const char *rpm, FD_t outfd, string &dirtag,
		       bool fullFileList, bool op_update,
		       map<string,UpdateInfo> &updateInfo,
		       set<string> &usefulFiles, struct HashCaches &caches,
		       FILE *idxfp, int changelog_since)
{
    struct stat sb;
    if (stat(rpm, &sb) < 0) {
	cerr << "genpkglist: " << rpm << ": " << strerror(errno) << endl;
	return 1;
    }

    Header h = readHeader(rpm);
    if (h == NULL) {
	FD_t infd = Fopen(rpm, "r");
	h = headerRead(infd, HEADER_MAGIC_YES);
	Fclose(infd);
    }
    if (h == NULL) {
	cerr << "genpkglist: " << rpm << ": cannot read package header" << endl;
	return 1;
    }

    Header newHeader = headerNew();
    copyTags(h, newHeader, numTags, tags);
    if (!fullFileList)
	copyStrippedFileList(h, newHeader, usefulFiles);
    else {
	copyTag(h, newHeader, RPMTAG_BASENAMES);
	copyTag(h, newHeader, RPMTAG_DIRNAMES);
	copyTag(h, newHeader, RPMTAG_DIRINDEXES);
    }
    if (changelog_since > 0)
	copyChangelog(h, newHeader, changelog_since);

    addAptTags(newHeader, dirtag.c_str(), rpm, sb.st_size);
    if (op_update)
	addInfoTags(newHeader, rpm, updateInfo);

    if (caches.md5) {
       char md5[34];
       if (!caches.md5->HashForFile(rpm, sb.st_mtime, md5)) {
	  cerr << "genpkglist: " << rpm << ": cannot calculate hash" << endl;
	  return 1;
       }
       headerPutString(newHeader, CRPMTAG_MD5, md5);
    }

    if (caches.blake2b) {
       char blake2b[129];
       if (!caches.blake2b->HashForFile(rpm, sb.st_mtime, blake2b)) {
	  cerr << "genpkglist: " << rpm << ": cannot calculate hash" << endl;
	  return 1;
       }
       headerPutString(newHeader, CRPMTAG_BLAKE2B, blake2b);
    }

    if (idxfp) {
	const char *srpm = headerGetString(h, RPMTAG_SOURCERPM);
	const char *name = headerGetString(h, RPMTAG_NAME);
	if (srpm && name)
	    fprintf(idxfp, "%s %s\n", srpm, name);
    }

    headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
    headerFree(newHeader);
    headerFree(h);
    return 0;
}


int main(int argc, char ** argv) 
{
   string rpmsdir;
   string pkglist_path;
   string basedir = "base";
   struct dirent **dirEntries;
   int entry_no = 0, entry_cur;
   char *op_dir;
   char *op_comp;
   char *op_index = NULL;
   char *op_usefulFiles = NULL;
   char *op_add_list = NULL;
   char *op_rm_list = NULL;
   char *op_update = NULL;
   int i;
   long /* time_t */ changelog_since = 0;
   bool fullFileList = false;
   bool noScan = false;
   bool progressBar = false;
   bool add_blake2b = false;
   bool hash_caching = true;

   bool prevStdin = false;

   putenv((char *)"LC_ALL="); // Is this necessary yet (after i18n was supported)?
   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "--basedir") == 0) {
	 i++;
	 if (i < argc) {
	    basedir = argv[i];
	 } else {
	    cerr << "genpkglist: directory missing for option --basedir"<<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--index") == 0) {
	 i++;
	 if (i < argc) {
	    op_index = argv[i];
	 } else {
	    cerr << "genpkglist: filename missing for option --index"<<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--info") == 0) {
	 i++;
	 if (i < argc) {
	    op_update = argv[i];
	 } else {
	    cerr << "genpkglist: filename missing for option --info"<<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--useful-files") == 0) {
	 i++;
	 if (i < argc) {
	    op_usefulFiles = argv[i];
	 } else {
	    cerr << "genpkglist: filename missing for option --useful-files"<<endl;
	    return 1;
	 }
      } else if (strcmp(argv[i], "--add-list") == 0) {
	 i++;
	 if (i < argc) {
	    op_add_list = argv[i];
	 } else {
	    cerr << "genpkglist: filename missing for option --add-list"<<endl;
	    return 1;
	 }
      } else if (strcmp(argv[i], "--rm-list") == 0) {
	 i++;
	 if (i < argc) {
	    op_rm_list = argv[i];
	 } else {
	    cerr << "genpkglist: filename missing for option --rm-list"<<endl;
	    return 1;
	 }
      } else if (strcmp(argv[i], "--changelog-since") == 0) {
	 i++;
	 if (i < argc) {
	    changelog_since = atol(argv[i]);
	 } else {
	    cerr << "genpkglist: argument missing for option --changelog-since" <<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--bloat") == 0) {
	 fullFileList = true;
      } else if (strcmp(argv[i], "--no-scan") == 0) {
	 noScan = true;
      } else if (strcmp(argv[i], "--progress") == 0) {
	 progressBar = true;
      } else if (strcmp(argv[i], "--blake2b") == 0) {
	 add_blake2b = true;
      } else if (strcmp(argv[i], "--disable-hash-caching") == 0) {
	 hash_caching = false;
      } else if (strcmp(argv[i], "--cachedir") == 0) {
	 i++;
	 if (i < argc) {
            _config->Set("Dir::Cache", argv[i]);
	 } else {
            cerr << "genpkglist: argument missing for option --cachedir"<<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--prev-stdin") == 0) {
	 prevStdin = true;
      } else if (strcmp(argv[i], "--help") == 0) {
	  usage(0);
      } else {
	 break;
      }
   }
   if (argc - i > 0)
       op_dir = argv[i++];
   else {
      usage(1);
   }
   if (argc - i > 0)
       op_comp = argv[i++];
   else {
      usage(1);
   }
   if (argc != i) {
      usage(1);
   }

   if (!prevStdin && (op_add_list || op_rm_list)) {
      cerr << "genpkglist: --add-list and --rm-list don't make sense without --prev-stdin" << endl;
   }

   map<string,UpdateInfo> updateInfo;
   if (op_update) {
      if (!loadUpdateInfo(op_update, updateInfo)) {
	 cerr << "genpkglist: error reading update info from file " << op_update << endl;
	 _error->DumpErrors();
	 exit(1);
      }
   }

   FILE *idxfp = NULL;
   if (op_index) {
      idxfp = fopen(op_index, "w+");
      if (!idxfp) {
	 cerr << "genpkglist: could not open " << op_index << " for writing";
	 perror("");
	 exit(1);
      }
   }

   {
      char cwd[PATH_MAX];
      
      if (getcwd(cwd, PATH_MAX) == 0)
      {
         cerr << argv[0] << ": " << strerror(errno) << endl;
         exit(1);
      }
      if (*op_dir != '/') {
	 rpmsdir = string(cwd) + "/" + string(op_dir);
      } else {
	 rpmsdir = string(op_dir);
      }
   }
   pkglist_path = string(rpmsdir) + "/" + basedir;
   rpmsdir = rpmsdir + "/RPMS." + string(op_comp);

   string dirtag = "RPMS." + string(op_comp);

   if (chdir(rpmsdir.c_str()) != 0)
   {
      cerr << "genpkglist: chdir(" << rpmsdir << "): " << strerror(errno) << endl;
      return 1;
   }

   if (!prevStdin && (entry_no = scandir(".", &dirEntries, selectRPMs, alphasort)) < 0)
   {
      cerr << "genpkglist: scandir(" << rpmsdir << "): " << strerror(errno) << endl;
      return 1;
   }

   pkglist_path = pkglist_path + "/pkglist." + op_comp;

   FD_t outfd = Fopen(pkglist_path.c_str(), "w+");
   if (!outfd) {
      cerr << "genpkglist: error creating file " << pkglist_path << ": "
	  << strerror(errno) << endl;
      return 1;
   }

   FD_t prevfd = NULL;
   if (prevStdin) {
      prevfd = fdDup(0);
      if (Ferror(prevfd)) {
	 cerr << "genpkglist: cannot open stdin" << endl;
	 return 1;
      }
   }

   set<string> usefulFiles;
   if (op_usefulFiles) {
      ifstream strm(op_usefulFiles);
      if (!strm) {
	 cerr << "genpkglist: cannot open " << op_usefulFiles <<endl;
	 return 1;
      }
      string line;
      while (std::getline(strm, line))
	 usefulFiles.insert(line);
   }

   ifstream add_strm;
   if (op_add_list) {
       add_strm = ifstream(op_add_list);
       if (!add_strm) {
	   cerr << "genpkglist: cannot open " << op_add_list <<endl;
	   return 1;
       }
   }

   ifstream rm_strm;
   if (op_rm_list) {
       rm_strm = ifstream(op_rm_list);
       if (!rm_strm) {
	   cerr << "genpkglist: cannot open " << op_rm_list <<endl;
	   return 1;
       }
   }

   struct rec *recs = NULL;
   int nrec = 0;

   if (entry_no > 0)
      recs = new struct rec[entry_no];

   for (entry_cur = 0; entry_cur < entry_no; entry_cur++) {

      if (progressBar)
	 simpleProgress(entry_cur + 1, entry_no);

      const char *rpm = dirEntries[entry_cur]->d_name;

      Header h = readHeader(rpm);
      if (h == NULL) {
	 FD_t infd = Fopen(rpm, "r");
	 h = headerRead(infd, HEADER_MAGIC_YES);
	 Fclose(infd);
      }
      if (h == NULL) {
	 cerr << "genpkglist: " << rpm << ": cannot read package header" << endl;
	 return 1;
      }

      const char *srpm = headerGetString(h, RPMTAG_SOURCERPM);
      if (srpm == NULL) {
	 cerr << "genpkglist: " << rpm << ": invalid binary package" << endl;
	 headerFree(h);
	 return 1;
      }
      srpm = strdup(srpm);
      if (srpm == NULL) {
	 cerr << "genpkglist: " << strerror(errno) << endl;
	 return 1;
      }

      recs[nrec].rpm = rpm;
      recs[nrec].srpm = srpm;
      nrec++;

      if (!(fullFileList || noScan)) {
	 findDepFiles(h, usefulFiles, RPMTAG_REQUIRENAME);
	 findDepFiles(h, usefulFiles, RPMTAG_PROVIDENAME);
	 findDepFiles(h, usefulFiles, RPMTAG_CONFLICTNAME);
	 findDepFiles(h, usefulFiles, RPMTAG_OBSOLETENAME);
      }

      headerFree(h);
   }

   if (nrec > 1)
      qsort(recs, nrec, sizeof(recs[0]), recCmp);

   string CachePrefix;
   if (hash_caching)
      CachePrefix = string(op_dir) + string(op_comp);

   struct HashCaches caches = {};
   caches.md5 = new CachedHash(PGPHASHALGO_MD5, CachePrefix, "genpkglist", ".md5cache");

   if (add_blake2b)
       caches.blake2b = new CachedHash(PGPHASHALGO_BLAKE2B, CachePrefix, "genpkglist", ".blake2bcache");

   for (int reci = 0; reci < nrec; reci++) {

      if (progressBar)
	 simpleProgress(reci + 1, nrec);

      const char *rpm = recs[reci].rpm;

      if (processPackageFile(rpm, outfd, dirtag, fullFileList, op_update,
			      updateInfo, usefulFiles, caches, idxfp,
			      changelog_since))
	  return 1;
   }

   if (prevStdin) {
       Header h;
       struct rec old_rec;
       struct rec add_rec, rm_rec;
       int progress = 0;

       read_list_line(add_strm, &add_rec);
       read_list_line(rm_strm, &rm_rec);
       h = headerRead(prevfd, HEADER_MAGIC_YES);

       while (1) {
	   if (progressBar)
	       simpleProgress(progress, 0);

	   if (h == NULL && add_rec.rpm == NULL && rm_rec.rpm == NULL)
	       break;

	   old_rec.rpm = headerGetString(h, CRPMTAG_FILENAME);
	   old_rec.srpm = headerGetString(h, RPMTAG_SOURCERPM);

	   if (h && (old_rec.rpm == NULL || old_rec.srpm == NULL)) {
	       cerr << "genpkglist: bad input from stdin (rpm)" << endl;
	       headerFree(h);
	       return 1;
	   }

	   if (rm_rec.rpm != NULL) {
	       if (!old_rec.rpm) {
		   cerr << "genpkglist: failed to remove " << string(rm_rec.rpm) << " package" << endl;
		   return 1;
	       }
	       int c = recCmp(&old_rec, &rm_rec);
	       if (c == 0) {
		   read_list_line(rm_strm, &rm_rec);
		   h = headerRead(prevfd, HEADER_MAGIC_YES);
		   continue;
	       } else if (c > 0) {
		   cerr << "genpkglist: failed to remove " << string(rm_rec.rpm) << " package" << endl;
		   headerFree(h);
		   return 1;
	       }
	   }

	   if (add_rec.rpm != NULL) {
	       int c = 1;
	       if (old_rec.rpm)
		   c = recCmp(&old_rec, &add_rec);
	       if (c == 0) {
		   cerr << "genpkglist: unexpected header " << string(old_rec.rpm) << " in package from stdin" << endl;
		   headerFree(h);
		   return 1;
	       }
	       else if (c > 0) {
		   if (processPackageFile(add_rec.rpm, outfd, dirtag,
					  fullFileList, op_update, updateInfo,
					  usefulFiles, caches, idxfp,
					  changelog_since))
		       return 1;
		   progress++;
		   read_list_line(add_strm, &add_rec);
		   continue;
	       }
	   }

	   Header newHeader = headerNew();
	   copyTags(h, newHeader, numTags, tags);
	   copyTags(h, newHeader, numAddTags, add_tags);

	   if (!copyTag(h, newHeader, CRPMTAG_MD5)) {
	      cerr << "genpkglist: old pkglist does not contain MD5 hash for " << string(old_rec.rpm) << endl;
	   }

	   if (add_blake2b) {
	      if (!copyTag(h, newHeader, CRPMTAG_BLAKE2B)) {
		 cerr << "genpkglist: old pkglist does not contain BLAKE2b hash for " << string(old_rec.rpm) << endl;
		 return 1;
	      }
	   }

	   if (!fullFileList) {
	       copyStrippedFileList(h, newHeader, usefulFiles);
	   } else {
	       /*
		* We do not really need to modify the header here, but rpm
		* changes the whole header layout in headerRead function, so we
		* have to copy all tags to restore the layout.
		*/
	       copyTag(h, newHeader, RPMTAG_BASENAMES);
	       copyTag(h, newHeader, RPMTAG_DIRNAMES);
	       copyTag(h, newHeader, RPMTAG_DIRINDEXES);
	   }
	   headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
	   headerFree(newHeader);

	   progress++;
	   headerFree(h);
	   h = headerRead(prevfd, HEADER_MAGIC_YES);
       }
   }

   Fclose(outfd);

   delete caches.md5;
   delete caches.blake2b;

   return 0;
}
